-- -------------------------------------------------------------------------
-- Utils/HeistHelper.lua (Heist Tool Helper Functions)
-- Purpose: Unified API wrapper for heist operations
-- Maintenance: Update this file when LEXIS API changes
-- -------------------------------------------------------------------------

local HeistHelper = {}

-- -------------------------------------------------------------------------
-- [Dependencies]
-- -------------------------------------------------------------------------

local debugger = nil

-- -------------------------------------------------------------------------
-- [Initialization]
-- -------------------------------------------------------------------------

function HeistHelper.init(debug_lib)
    debugger = debug_lib
    if debugger then
        debugger.info("[HeistHelper] Initialized")
    end
    return true
end

-- -------------------------------------------------------------------------
-- [Character Helper]
-- -------------------------------------------------------------------------

--- Get current character prefix (MP0_ or MP1_)
---@return string prefix Character stat prefix
function HeistHelper.get_prefix()
    local char = 0
    if account and account.character then
        char = account.character()
    elseif script and script.globals then
        -- Fallback: read from global
        char = script.globals(1574927).int32 or 0
    end
    if char ~= 0 and char ~= 1 then char = 0 end
    return "MP" .. char .. "_"
end

--- Get current character index (0 or 1)
---@return integer char Character index
function HeistHelper.get_character()
    if account and account.character then
        return account.character()
    end
    return 0
end

-- -------------------------------------------------------------------------
-- [Stats API Wrapper]
-- -------------------------------------------------------------------------

--- Set a stat value (auto-prefix with character)
---@param name string Stat name without prefix (e.g., "FIXER_STORY_BS")
---@param value integer|boolean Value to set
---@param value_type? string Type: "int32", "bool" (default: "int32")
function HeistHelper.set_stat(name, value, value_type)
    value_type = value_type or "int32"
    local full_name = HeistHelper.get_prefix() .. name

    if account and account.stats then
        local stat = account.stats(full_name)
        if value_type == "bool" then
            stat.bool = value
        else
            stat.int32 = value
        end
    end
end

--- Get a stat value (auto-prefix with character)
---@param name string Stat name without prefix
---@param value_type? string Type: "int32", "bool" (default: "int32")
---@return integer|boolean value Stat value
function HeistHelper.get_stat(name, value_type)
    value_type = value_type or "int32"
    local full_name = HeistHelper.get_prefix() .. name

    if account and account.stats then
        local stat = account.stats(full_name)
        if value_type == "bool" then
            return stat.bool
        else
            return stat.int32
        end
    end
    return 0
end

--- Set a stat value for both characters
---@param name string Stat name without prefix
---@param value integer|boolean Value to set
---@param value_type? string Type: "int32", "bool" (default: "int32")
function HeistHelper.set_stat_both(name, value, value_type)
    value_type = value_type or "int32"

    if account and account.stats then
        for _, prefix in ipairs({"MP0_", "MP1_"}) do
            local stat = account.stats(prefix .. name)
            if value_type == "bool" then
                stat.bool = value
            else
                stat.int32 = value
            end
        end
    end
end

--- Set a stat value with full name (no auto-prefix)
--- Use for MPPLY_ stats or other non-character stats
---@param full_name string Full stat name (e.g., "MPPLY_HEISTFLOWORDERPROGRESS")
---@param value integer|boolean Value to set
---@param value_type? string Type: "int32", "bool" (default: "int32")
function HeistHelper.set_stat_raw(full_name, value, value_type)
    value_type = value_type or "int32"

    if account and account.stats then
        local stat = account.stats(full_name)
        if value_type == "bool" then
            stat.bool = value
        else
            stat.int32 = value
        end
    end
end

-- -------------------------------------------------------------------------
-- [Globals API Wrapper]
-- -------------------------------------------------------------------------

--- Set a global value
---@param index integer Global index
---@param value integer|boolean|number Value to set
---@param value_type? string Type: "int32", "bool", "float" (default: "int32")
function HeistHelper.set_global(index, value, value_type)
    value_type = value_type or "int32"

    if script and script.globals then
        local global = script.globals(index)
        if value_type == "bool" then
            global.bool = value
        elseif value_type == "float" then
            global.float = value
        else
            global.int32 = value
        end
    end
end

--- Get a global value
---@param index integer Global index
---@param value_type? string Type: "int32", "bool", "float" (default: "int32")
---@return integer|boolean|number value Global value
function HeistHelper.get_global(index, value_type)
    value_type = value_type or "int32"

    if script and script.globals then
        local global = script.globals(index)
        if value_type == "bool" then
            return global.bool
        elseif value_type == "float" then
            return global.float
        else
            return global.int32
        end
    end
    return 0
end

--- Set global with offset calculation
---@param base integer Base global index
---@param offset integer Offset from base
---@param value integer Value to set
function HeistHelper.set_global_at(base, offset, value)
    HeistHelper.set_global(base + offset, value)
end

-- -------------------------------------------------------------------------
-- [Locals API Wrapper]
-- -------------------------------------------------------------------------

--- Set a script local value
---@param script_name string Script name (e.g., "fm_mission_controller")
---@param index integer Local index
---@param value integer|boolean|number Value to set
---@param value_type? string Type: "int32", "bool", "float" (default: "int32")
function HeistHelper.set_local(script_name, index, value, value_type)
    value_type = value_type or "int32"

    if script and script.locals and script.running then
        if not script.running(script_name) then return end

        local loc = script.locals(script_name, index)
        if loc then
            if value_type == "bool" then
                loc.bool = value
            elseif value_type == "float" then
                loc.float = value
            else
                loc.int32 = value
            end
        end
    end
end

--- Get a script local value
---@param script_name string Script name
---@param index integer Local index
---@param value_type? string Type (default: "int32")
---@return integer|boolean|number|nil value Local value or nil if script not running
function HeistHelper.get_local(script_name, index, value_type)
    value_type = value_type or "int32"

    -- Validate parameters
    if not script_name or type(script_name) ~= "string" then
        if debugger then
            debugger.error("[HeistHelper] get_local: invalid script_name: " .. tostring(script_name))
        end
        return nil
    end
    if not index or type(index) ~= "number" then
        if debugger then
            debugger.error("[HeistHelper] get_local: invalid index: " .. tostring(index))
        end
        return nil
    end

    if script and script.locals and script.running then
        if not script.running(script_name) then return nil end

        local loc = script.locals(script_name, index)
        if loc then
            if value_type == "bool" then
                return loc.bool
            elseif value_type == "float" then
                return loc.float
            else
                return loc.int32
            end
        end
    end
    return nil
end

-- -------------------------------------------------------------------------
-- [Script Utilities]
-- -------------------------------------------------------------------------

--- Check if a script is running
---@param script_name string Script name
---@return boolean running True if script is running
function HeistHelper.is_script_running(script_name)
    if script and script.running then
        return script.running(script_name)
    end
    return false
end

--- Force host on a script
---@param script_name string Script name
---@return boolean success True if successful
function HeistHelper.force_host(script_name)
    if script and script.force_host then
        return script.force_host(script_name)
    end
    return false
end

-- -------------------------------------------------------------------------
-- [Tunables API Wrapper]
-- -------------------------------------------------------------------------

--- Set a tunable value
---@param name string Tunable name
---@param value integer|number Value to set
---@param value_type? string Type: "int32", "float" (default: "int32")
function HeistHelper.set_tunable(name, value, value_type)
    value_type = value_type or "int32"

    if script and script.tunables then
        local tunable = script.tunables(name)
        if value_type == "float" then
            tunable.float = value
        else
            tunable.int32 = value
        end
    end
end

--- Reset a tunable to its default value
---@param name string|integer Tunable name or hash
function HeistHelper.reset_tunable(name)
    -- Default values for common tunables (from Cherax eTunable.lua)
    local DEFAULTS = {
        -- Cayo Perico
        ["IH_LOOT_BAG_MAX_CAPACITY"] = 1800,
        ["HEIST_BAG_MAX_CAPACITY"] = 1800,
        ["IH_FENCING_FEE_DIVIDE"] = -0.1,
        ["IH_DEDUCTION_FENCING_FEE"] = -0.1,
        ["IH_DEDUCTION_PAVEL_CUT"] = -0.02,
        -- Casino
        ["CH_LOOT_BAG_LIMIT"] = 0,
        ["CH_LESTER_CUT"] = 5,
        ["CH_CREW_CUT_MINIMUM"] = 15,
        ["CH_CREW_CUT_MAXIMUM"] = 50,
        -- Gunman cuts
        ["CH_CREW_CUT_GUNMAN_1"] = 5,
        ["CH_CREW_CUT_GUNMAN_2"] = 9,
        ["CH_CREW_CUT_GUNMAN_3"] = 7,
        ["CH_CREW_CUT_GUNMAN_4"] = 10,
        ["CH_CREW_CUT_GUNMAN_5"] = 8,
        -- Driver cuts
        ["CH_CREW_CUT_DRIVER_1"] = 5,
        ["CH_CREW_CUT_DRIVER_2"] = 7,
        ["CH_CREW_CUT_DRIVER_3"] = 9,
        ["CH_CREW_CUT_DRIVER_4"] = 6,
        ["CH_CREW_CUT_DRIVER_5"] = 10,
        -- Hacker cuts
        ["CH_CREW_CUT_HACKER_1"] = 3,
        ["CH_CREW_CUT_HACKER_2"] = 7,
        ["CH_CREW_CUT_HACKER_3"] = 5,
        ["CH_CREW_CUT_HACKER_4"] = 10,
        ["CH_CREW_CUT_HACKER_5"] = 9,
    }

    if script and script.tunables then
        local default_val = DEFAULTS[name]
        if default_val then
            local tunable = script.tunables(name)
            if type(default_val) == "number" and default_val ~= math.floor(default_val) then
                tunable.float = default_val
            else
                tunable.int32 = default_val
            end
        end
    end
end

--- Get a tunable value
---@param name string Tunable name
---@param value_type? string Type (default: "int32")
---@return integer|number value Tunable value
function HeistHelper.get_tunable(name, value_type)
    value_type = value_type or "int32"

    if script and script.tunables then
        local tunable = script.tunables(name)
        if value_type == "float" then
            return tunable.float
        else
            return tunable.int32
        end
    end
    return 0
end

-- -------------------------------------------------------------------------
-- [Bit Operations]
-- -------------------------------------------------------------------------

--- Set a bit in a value
---@param value integer Original value
---@param bit integer Bit position (0-based)
---@return integer result Value with bit set
function HeistHelper.set_bit(value, bit)
    return value | (1 << bit)
end

--- Clear a bit in a value
---@param value integer Original value
---@param bit integer Bit position (0-based)
---@return integer result Value with bit cleared
function HeistHelper.clear_bit(value, bit)
    return value & ~(1 << bit)
end

--- Check if a bit is set
---@param value integer Value to check
---@param bit integer Bit position (0-based)
---@return boolean is_set True if bit is set
function HeistHelper.is_bit_set(value, bit)
    return (value & (1 << bit)) ~= 0
end

-- -------------------------------------------------------------------------
-- [Batch Operations - For Heist Preps]
-- -------------------------------------------------------------------------

--- Apply multiple stats at once
---@param stats table Array of {name, value, type?} tables
function HeistHelper.set_stats_batch(stats)
    for _, stat in ipairs(stats) do
        HeistHelper.set_stat(stat[1], stat[2], stat[3])
    end
end

--- Apply multiple stats to both characters
---@param stats table Array of {name, value, type?} tables
function HeistHelper.set_stats_both_batch(stats)
    for _, stat in ipairs(stats) do
        HeistHelper.set_stat_both(stat[1], stat[2], stat[3])
    end
end

--- Reload planning board (common operation)
---@param script_name string Planning script name
---@param local_index integer Local index for reload trigger
function HeistHelper.reload_board(script_name, local_index)
    HeistHelper.set_local(script_name, local_index, 2)
end

-- Alias: set_tunable_by_hash (script.tunables() accepts both name string and hash ID)
HeistHelper.set_tunable_by_hash = HeistHelper.set_tunable

return HeistHelper
